package db.manager;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Queue;

public class DBManager  {

    private static final String DB_NUMBER_OF_CONNECTIONS_PROPERTY = "db_number_of_connections";
    private static final String JDBC_DRIVER_PROPERTY = "jdbc_driver";
    private static final String DB_URL_PROPERTY = "db_url";
    private static final String DB_PASSWORD_PROPERTY = "db_password";
    private static final String DB_USERNAME_PROPERTY = "db_username";
    private static final String DB_PROPERTIES = "config/db.properties";
    
    private static DBManager manager;
    
    private boolean initialized = false;
    
    private String username;
    private String password;
    private String url;
    private String driver;
    private int numberOfConnections;
    
    private Queue<Connection> connections;
    private Map<Connection, Boolean> usedConnections;
    
    private DBManager() { }
    
    public void init() throws IOException, SQLException {
        
        if (initialized) {
            throw new IllegalStateException("DBManager was already initialized");
        }
        
        Properties props = new Properties();
        File file = new File(DB_PROPERTIES);
        InputStream is = new FileInputStream(file);
        
        props.load(is);
        
        username = props.getProperty(DB_USERNAME_PROPERTY);
        if (username == null) {
            throw new IllegalStateException("Missing username property");
        }
        
        password = props.getProperty(DB_PASSWORD_PROPERTY);
        if (password == null) {
            throw new IllegalStateException("Missing password property");
        }
        
        url = props.getProperty(DB_URL_PROPERTY);
        if (url == null) {
            throw new IllegalStateException("Missing url property");
        }
        
        driver = props.getProperty(JDBC_DRIVER_PROPERTY);
        if (driver == null) {
            throw new IllegalStateException("Missing driver property");
        }

        String rawNumberOfConnections = props.getProperty(DB_NUMBER_OF_CONNECTIONS_PROPERTY);
        if (rawNumberOfConnections == null) {
            throw new IllegalStateException("Missing number of connections property");
        }
        try {
            numberOfConnections = Integer.parseInt(rawNumberOfConnections);
        } catch (NumberFormatException e) {
            throw new IllegalStateException("Failed parsing number of connections");
        }
        if (numberOfConnections < 1) {
            throw new IllegalArgumentException("Number of connections has to be positive");
        }
        
        try {
            Class.forName(driver);
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException("Failed finding jdbc driver in classpath");
        }
        
        connections = new ArrayDeque<Connection>();
        usedConnections = new HashMap<Connection, Boolean>();
        
        for (int i=0; i<numberOfConnections; i++) { 
            Connection connection = DriverManager.getConnection(url, username, password);
            connections.add(connection);
            usedConnections.put(connection, false);
        }
        
        initialized = true;
    }
    
    public boolean isInitialized() {
        return initialized;
    }
    
    public synchronized void close() throws SQLException {
        if (usedConnections != null) {
            for (Connection connection : usedConnections.keySet()) {
                connection.close();
            }
        }
        manager = null;
    }
    
    public static DBManager getInstance() {
        if (manager == null) {
            manager = new DBManager();
        }
        return manager;
    }

    public synchronized Connection poolConnection() {
        
        if (!initialized) {
            throw new IllegalStateException("DBMananger must be initialized before usage");
        }
        
        Connection connection = connections.poll();
        
        if (connection == null) {
            return null;
        }
        
        usedConnections.put(connection, true);
        return connection;
        
    }
    
    public synchronized void returnConnection(Connection connection) {
        
        if (!initialized) {
            throw new IllegalStateException("DBMananger must be initialized before usage");
        }
        
        if (connection == null) {
            throw new IllegalArgumentException("connection cannot be null");
        }
        
        if (usedConnections.get(connection) == null) {
            throw new IllegalArgumentException("Unknown connection return to connection pool");
        }
        
        if (!usedConnections.get(connection)) {
            throw new IllegalArgumentException("Connection is already in connection pool");
        }
        
        usedConnections.put(connection, false);
        
        connections.add(connection);
    }
    
}
